package com.aispeech.asr.comm.utils;

import com.aispeech.asr.comm.exceptions.AsrException;
import lombok.extern.slf4j.Slf4j;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class BeanUtils {
    private static final Map<Class<?>, BeanInfo> cachedBeanInfoMap = new ConcurrentHashMap<>();

    private static <T> BeanInfo getBeanInfo(Class<T> clazz) throws IntrospectionException {
        BeanInfo beanInfo = cachedBeanInfoMap.get(clazz);

        if (beanInfo == null) {
            beanInfo = Introspector.getBeanInfo(clazz, Object.class);
            cachedBeanInfoMap.put(clazz, beanInfo);
        }

        return beanInfo;
    }

    public static Map<String, Object> toMap(Object bean) throws Exception {
        Map<String, Object> map = new HashMap<>();

        BeanInfo beanInfo = getBeanInfo(bean.getClass());
        for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
            String key = descriptor.getName();
            Method method = descriptor.getReadMethod();
            Object value = method.invoke(bean);

            map.put(key, value);
        }

        return map;
    }

    public static <T, V> T mapToBean(Map<String, V> map, Class<T> clazz) throws Exception {
        T bean = clazz.newInstance();

        BeanInfo beanInfo = getBeanInfo(clazz);
        for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
            String key = humpToSnake(descriptor.getName());
            Object value = map.get(key);
            Method method = descriptor.getWriteMethod();

            if (value != null) {
                String typeName = descriptor.getPropertyType().getTypeName();

                try {
                    switch (typeName) {
                        case "java.lang.Integer":
                        case "int":
                            value = Integer.parseInt(value.toString());
                            break;
                        case "java.lang.Long":
                        case "long":
                            value = Long.parseLong(value.toString());
                            break;
                        case "java.lang.Double":
                        case "double":
                            value = Double.parseDouble(value.toString());
                            break;
                        case "java.util.Date":
                            value = new SimpleDateFormat("yyyy-MM-dd").parse(value.toString());
                            break;
                    }

                    method.invoke(bean, value);
                } catch (NumberFormatException ex) {
                    throw new AsrException(String.format("key: %s, unexpected value: %s. caused by: %s", key, value, ex.getMessage()));
                }
            }
        }

        return bean;
    }

    public static String humpToSnake(String source) {
        StringBuilder stringBuilder = new StringBuilder(source.length());

        stringBuilder.append(Character.toLowerCase(source.charAt(0)));
        for (int i = 1; i < source.length(); i++) {
            char ch = source.charAt(i);

            if (Character.isUpperCase(ch)) {
                stringBuilder.append('_');
                stringBuilder.append(Character.toLowerCase(ch));
            } else {
                stringBuilder.append(ch);
            }
        }

        return stringBuilder.toString();
    }

    public static long timeoutByAudioLength(long length) {
        long mbFive = 5242880L;                // 5M
        long secsBase =  30;                      // 30secs

        long secs = secsBase * Math.min(60, length / mbFive);
        secs = Math.max(secs, 180);// 最少3分钟

        return secs - 30;          //比task handler少30s，防止handler提前超时
    }

    public static long timeoutByDuration(long duration) {
        long fiveMins = 300;           // 5Min
        long secsBase =  60;          // 30secs

        long msecs = secsBase * Math.min(30, duration / (fiveMins * 1000));
        msecs = Math.max(msecs, 180);// 最少3分钟

        return msecs;
    }

    public static void main(String[] args) {
        long timeout = timeoutByDuration(60 * 1000 * 30);
//        long timeout = timeoutByAudioLength(100);
        log.info("res: {}", timeout);
    }
}
